﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MyAlbum.Model;
using System.Xml.Linq;
using MyAlbum.Utility;


namespace MyAlbum.Data.Xml
{
    public class EntryRepository : XmlRepository, IEntryRepository
    {
        private static object locker = "locker";
        private TagEntryRepository _tagsRepository;
        private ReplyRepository _repliesRepository;
        public EntryRepository(string rootPath, 
            TagEntryRepository tagRepository,
            ReplyRepository repliesRepository)
            : base(rootPath, "Entries")
        {
            this._tagsRepository = tagRepository;
            this._repliesRepository = repliesRepository;
        }
        public int CountItems(bool visible)
        {
            return this.Document.Descendants("Entry").Where(a => visible ? (bool)a.Element("Visible") : true).Count();
        }
        /// <summary>
        /// Add a new tag if the tag does not exist, else increase it's count. 
        /// Add a new relation if non was found.
        /// </summary>
        /// <param name="entryId">The entry id</param>
        /// <param name="tags">The tag name</param>   
        public void AddTagsForEntry(string entryId, string tagName)
        {           
            this._tagsRepository.AddTag(entryId, tagName);
        }




        public List<MyAlbum.Model.Entry> GetItems()
        {
            return GetItems(1, int.MaxValue, true);
        }

        public List<MonthSummary> GetEntriesByMonthsSummary(bool visible)
        {
            Func<XElement, bool> filter;
            if (visible)
            {
                filter = e => (bool)e.Element("Visible");

            }
            else
            {
                filter = e => true;
            }
            return (from e in this.Document.Descendants("Entry").Where(filter)
                    group e by
                    new { ((DateTime)e.Element("Date")).Year, ((DateTime)e.Element("Date")).Month } into g
                    orderby g.Key.Year, g.Key.Month
                    select new MonthSummary { Month = g.Key.Month, Year = g.Key.Year, Count = g.Count() }).ToList();
        }

        private IEnumerable<MyAlbum.Model.Entry> LoadItems(IEnumerable<XElement> objectQuery, bool withTagsAndReplies)
        {
           
            return objectQuery.Select(e =>
                new MyAlbum.Model.Entry()
                {
                    ParentId = e.Element("ParentId").Value,
                    Description = e.Element("Description").Value,
                    EntryType = (int)e.Element("EntryType"),
                    FileSize = (int)e.Element("FileSize"),
                    Id = (e.Attribute("Id").Value),
                    Title = e.Element("Title").Value.Trim(),
                    Uri = e.Element("Uri").Value.Trim(),
                    VideoLen = (int)e.Element("VideoLen"),
                    Visible = (bool)e.Element("Visible"),
                    Width = (int)e.Element("Width"),
                    Height = (int)e.Element("Height"),
                    Date = (DateTime)e.Element("Date"),
                    ThumbHeight = (int)e.Element("ThumbHeight"),
                    ThumbUri = e.Element("ThumbUri").Value,
                    ThumbWidth = (int)e.Element("ThumbWidth"),
                    Tags = (withTagsAndReplies ? this._tagsRepository.GetTagsForEntry(e.Attribute("Id").Value) : null),
                    Replies = (withTagsAndReplies ? this._repliesRepository.GetItemsForEntry(
                        (e.Attribute("Id").Value)) : null)
                });

        }


        public List<MyAlbum.Model.Entry> GetEntriesByTagName(string tagName, bool visible)
        {
            var entries = this.Document.Descendants("Entry");

            var tagsIds = this._tagsRepository.GetEntriesIdsForTagName(tagName);
            Func<XElement, bool> select;
            if (visible)
            {
                select = e => tagsIds.Contains((e.Attribute("Id").Value)) && (bool)e.Element("Visible");

            }
            else
            {
                select = e => tagsIds.Contains((e.Attribute("Id").Value));
            }
            return LoadItems(this.Document.Descendants("Entry").Where(select), true).ToList();
        }

        public void AddItem(MyAlbum.Model.Entry item)
        {
            Assert.IsNotNull(item);
            Assert.IsNotNull(item.Title);
            lock (locker)
            {
                item.Id = Guid.NewGuid().ToString();
                XElement a = new XElement("Entry",
                    new XAttribute("Id", item.Id),
                    new XElement("Width", item.Width),
                    new XElement("Visible", item.Visible),
                    new XElement("VideoLen", item.VideoLen),
                    new XElement("Height", item.Height),
                    new XElement("Uri", item.Uri),
                    new XElement("FileSize", item.FileSize),
                    new XElement("EntryType", item.EntryType),
                    new XElement("Description", item.Description),
                    new XElement("ParentId", item.ParentId),
                    new XElement("Date", item.Date),
                    new XElement("ThumbWidth", item.ThumbWidth),
                    new XElement("ThumbUri", item.ThumbUri),
                    new XElement("ThumbHeight", item.ThumbHeight),
                    new XElement("Title", item.Title));
                Document.Element("Entries").Add(a);
                this.Save();
                AddTags(item);
            }

        }

        private void AddTags(MyAlbum.Model.Entry item)
        {
            Assert.IsNotNull(item);
            if (item.Tags != null)
            {
                foreach (var tag in item.Tags)
                {
                    AddTagsForEntry(item.Id, tag.Name);
                }
            }
        }

        public void DeleteItem(MyAlbum.Model.Entry item)
        {
            Assert.IsNotNull(item);
            var saved = (from e in Document.Descendants("Entry") where (e.Attribute("Id").Value) == item.Id select e).FirstOrDefault();
            if (saved != null)
            {
                RemoveTagsForEntry(item.Id);
                saved.Remove();
                this.Save();
            }
        }

        /// <summary>
        /// Removes all of the relations of the specified entry, 
        /// and decreases the count all of the relevant tags
        /// </summary>
        /// <param name="entryId">The entry id</param>
        public void RemoveTagsForEntry(string entryId)
        {
            this._tagsRepository.RemoveTagsForEntry(entryId);

        }

        public void UpdateItem(MyAlbum.Model.Entry item)
        {
            Assert.IsNotNull(item);
            var saved = (from e in Document.Descendants("Entry") where (e.Attribute("Id").Value) == item.Id select e).FirstOrDefault();
            if (saved != null)
            {
                saved.Element("Visible").ReplaceWith(new XElement("Visible", item.Visible));
                saved.Element("ParentId").ReplaceWith(new XElement("ParentId", item.ParentId));
                saved.Element("EntryType").ReplaceWith(new XElement("EntryType", item.EntryType));
                saved.Element("FileSize").ReplaceWith(new XElement("FileSize", item.FileSize));
                saved.Element("Description").ReplaceWith(new XElement("Description", item.Description));
                saved.Element("ThumbUri").ReplaceWith(new XElement("ThumbUri", item.ThumbUri));
                saved.Element("ThumbWidth").ReplaceWith(new XElement("ThumbWidth", item.ThumbWidth));
                saved.Element("ThumbHeight").ReplaceWith(new XElement("ThumbHeight", item.ThumbHeight));
                saved.Element("Width").ReplaceWith(new XElement("Width", item.Width));
                saved.Element("Uri").ReplaceWith(new XElement("Uri", item.Uri));
                saved.Element("VideoLen").ReplaceWith(new XElement("VideoLen", item.VideoLen));
                saved.Element("Title").ReplaceWith(new XElement("Title", item.Title));
                saved.Element("Height").ReplaceWith(new XElement("Height", item.Height));
                this.Save();
            }
            RemoveTagsForEntry(item.Id);
            AddTags(item);

        }

        public MyAlbum.Model.Entry GetItemById(string entryId)
        {
            return this.GetEntryById(entryId, true);
        }


        public MyAlbum.Model.Entry GetEntryById(string entryId, bool visible)
        {
            Func<XElement, bool> select;
            if (visible)
            {
                select = e => e.Attribute("Id").Value == entryId && (bool)e.Element("Visible");
            }
            else
            {
                select = e => e.Attribute("Id").Value == entryId;
            }
            return LoadItems(this.Document.Descendants("Entry").Where(select), true).SingleOrDefault();
        }

        public MyAlbum.Model.Entry GetLatestEntry(bool visible)
        {
            Func<XElement, bool> select;
            if (visible)
            {
                select = e => (bool)e.Element("Visible");
            }
            else
            {
                select = e => true;
            }
            return LoadItems(this.Document.Descendants("Entry").Where(select), true).LastOrDefault();
        }

        #region IEntryRepository Members


        public List<Entry> GetEntriesByAlbum(string albumId, int pageNumber, int count, bool visible)
        {
            Func<XElement, bool> select;
            if (visible)
            {
                select = e => e.Element("ParentId").Value == albumId && (bool)e.Element("Visible");
            }
            else
            {
                select = e => e.Element("ParentId").Value == albumId;
            }
            return LoadItems(this.Document.Descendants("Entry").Where(select).Skip((pageNumber - 1) * count).Take(count), true).ToList();
        }


        public int GetEntriesCount(bool visible)
        {
            Func<XElement, bool> select;
            if (visible)
            {
                select = e => (bool)e.Element("Visible");
            }
            else
            {
                select = e => true;
            }
            return this.Document.Descendants("Entry").Count();
        }

        public List<Entry> GetItems(int pageNumber, int count, bool visible)
        {
            var result = LoadItems(this.Document.Descendants("Entry").Where(e => (visible ? (bool)e.Element("Visible") : true)).Skip((pageNumber - 1) * count).Take(count), true);
            return result.ToList();
        }


        /// <summary>
        /// Load random visible entries summary (no replies or tags)
        /// </summary>
        /// <param name="count">The max number of entries to load</param>
        /// <returns>List of Entry</returns>
        public List<Entry> LoadRandomEntriesSummary(int count)
        {
            if (count <= 0)
            {
                return new List<Entry>();
            }
            else
            {
                var entries = this.Document.Descendants("Entry").Where(e => (bool)e.Element("Visible"));
                int maxSkip = Math.Max(0, entries.Count() - count);
                return (LoadItems(entries.Skip(new Random().Next(maxSkip)).Take(count), false).ToList());
            }
        }

        #endregion

        /// <summary>
        /// Load the latest entries
        /// </summary>
        /// <param name="count">The number of entries to load</param>
        /// <returns>List of Entry</returns>
        public List<Entry> GetLatestEntries(int count, bool visible)
        {
            Func<XElement, bool> select;
            if (visible)
            {
                select = e => (bool)e.Element("Visible");
            }
            else
            {
                select = e => true;
            }
            return LoadItems(this.Document.Descendants("Entry").Where(select).OrderByDescending(e => e.Attribute("Id").Value).Take(count), true).ToList();
        }
    }
}
